﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Linq;
using SuperDeepZoom;
using System.Windows.Browser;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Text;
using eventr.SPQueryService;
using System.Xml.Linq;
using System.Diagnostics;

namespace eventr.View
{
    /// <summary>
    /// This is an example of how you might like to control the SuperDeepZoom in your app. 
    /// This class manages the image data for the overlays, coordinates user operations and filtering
    /// </summary>
    public class MainPageViewModel : ViewModel
    {
        int? viewedImage = null;

        private const bool UseFASTSearch = false;

        List<Model.AssetResult> subImageMetadata;

        List<FlyThroughOverlaySegment> flyThroughOverlaySegments;

        List<string> allTags;

        List<int> filterIds = new List<int>();

        private const string RefinementAllValueText = "All";

        bool flyThroughOverlayEnabled = false;
        bool showFlyThoughInfoPopup = false;
        bool mostViewedMode = false;
        bool specialImageOverlayEnabled = false;

        SuperDeepZoom.SuperDeepZoom zoom;

        string filterTag;
        string flyThroughHash;
        string deepZoomSource;

        eventr.Model.AssetResult currentImageContext = null;

        Dictionary<int, object> imageContextData;

        public MainPageViewModel(SuperDeepZoom.SuperDeepZoom zoom)
        {
            this.zoom = zoom;
            zoom.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(zoom_PropertyChanged);
            zoom.MetadataLoaded += new EventHandler(zoom_MetadataLoaded);
            SpecialImageOverlayEnabled = true;
        }

        void zoom_MetadataLoaded(object sender, EventArgs e)
        {
            zoom.ShowAllImages();
        }

        public void Refresh()
        {
            imageContextData = null;
            subImageMetadata = null;
            DeepZoomSource = null;

            string queryServiceUrl = string.Format("{0}://{1}/_vti_bin/search.asmx", HtmlPage.Document.DocumentUri.Scheme, HtmlPage.Document.DocumentUri.Host);

            QueryServiceSoapClient client = new QueryServiceSoapClient("QueryServiceSoap", queryServiceUrl);
            client.QueryExCompleted += new EventHandler<QueryExCompletedEventArgs>(client_QueryExCompleted);

            IsSearching = true;

            string queryXml = BuildQueryXml(CurrentSearchTerm, SelectedRefinementValues, Guid.NewGuid().ToString("B"));

            client.QueryExAsync(queryXml);
        }

        void client_QueryExCompleted(object sender, QueryExCompletedEventArgs e)
        {
            ArrayOfXElement elements = e.Result;
            XElement schemaNode = elements.Nodes.First(n => n.Name.LocalName == "schema");
            XElement dgNode = elements.Nodes.First(n => n.Name.LocalName == "diffgram");
            XElement resultsNode = dgNode.Element("Results");

            int resultCount = 0;
            List<Model.AssetResult> assets = new List<Model.AssetResult>();

            if (resultsNode != null && resultsNode.HasElements)
            {
                foreach (XElement result in resultsNode.Elements())
                {
                    if (result.Element("DocId") != null)
                    {
                        Model.AssetResult ar = new Model.AssetResult();
                        ar.DeepZoomTileKey = result.Element("DocId").Value;

                        ar.Id = resultCount++;

                        ar.AssetType = result.Element("ContentTypeKey").Value.Contains("Video") ? "Video" : result.Element("ContentTypeKey").Value;
                        ar.Title = result.Element("Title").Value;
                        ar.Url = result.Element("Path").Value;
                        if (ar.AssetType == "Video")
                            ar.VideoUrl = result.Element("ItemUrl").Value;
                        ar.LastModified = result.Element("Write").Value;
                        ar.Properties = new List<Model.AssetPropertyValue>();

                        foreach (XElement prop in result.Elements())
                        {
                            Model.AssetPropertyValue apv = new Model.AssetPropertyValue();
                            apv.Name = prop.Name.LocalName;
                            apv.Value = prop.Value;
                            ar.Properties.Add(apv);
                        }

                        //DEBUGGING CODE
                        string[] temp = ar.Properties.Select(v => v.Name + " : " + v.Value).ToArray();

                        //DEBUGGING CODE

                        assets.Add(ar);
                    }
                }
            }

            if (assets.Count > 0)
            {
                subImageMetadata = assets.ToList();

                //NOTE: use the following code if you will be hosting the deepzoom web service in sharepoint
                //string dcmServiceUrl = string.Format("{0}://{1}/_vti_bin/dcm.asmx", HtmlPage.Document.DocumentUri.Scheme, HtmlPage.Document.DocumentUri.Host);
                //SPDeepZoom.DeepZoomTileServiceSoapClient client = new SPDeepZoom.DeepZoomTileServiceSoapClient("DeepZoomTileServiceSoap", dcmServiceUrl);
                
                //NOTE: use the following code if you will be hosting the deepzoom web service in it's own site
                SPDeepZoom.DeepZoomTileServiceSoapClient client = new SPDeepZoom.DeepZoomTileServiceSoapClient("DeepZoomTileServiceSoap");

                
                client.GenerateDeepZoomCollectionCompleted += new EventHandler<SPDeepZoom.GenerateDeepZoomCollectionCompletedEventArgs>(client_GenerateDeepZoomCollectionCompleted);
                SPDeepZoom.ArrayOfString keys = new SPDeepZoom.ArrayOfString();
                keys.AddRange(assets.Select(ar => ar.DeepZoomTileKey).ToList());
                client.GenerateDeepZoomCollectionAsync(keys);
            }
            else
            {
                imageContextData = null;
                subImageMetadata = null;
                DeepZoomSource = null;
                if (SearchCompleted != null)
                    SearchCompleted(this, new EventArgs());
                MessageBox.Show("No results found.");
            }
        }

        void client_GenerateDeepZoomCollectionCompleted(object sender, SPDeepZoom.GenerateDeepZoomCollectionCompletedEventArgs e)
        {
            List<string> tileKeys = e.Result.IncludedImageKeys.ToList();

            //remove metadata for any records that were not included in tile set
            subImageMetadata = subImageMetadata.Where(ar => tileKeys.Any(k => k == ar.DeepZoomTileKey)).ToList();

            //resequence the metadata image id's
            int newId = 0;
            foreach (var asset in subImageMetadata)
                asset.Id = newId++;

            #region refiner source builder (specific to enterprise search)
            List<Model.RefinementGroup> resultRefiners = new List<Model.RefinementGroup>();
            //TODO: cleanup this
            string[] refinementFields = { "Author", "AudioBitRate", "AudioLanguage", "VideoBitRate", "VideoFrameRate", "VideoDisplayAspectRatio", "VideoFormat", "Category" };
            string[] refinementFieldsFriendly = { "Author", "Audio Bit Rate", "Language", "Video Bit Rate", "Frame Rate", "Aspect Ratio", "Video Format", "Category" };

            for (int i = 0; i < refinementFields.Length; i++)
            {
                Model.RefinementGroup refiner = new Model.RefinementGroup();
                refiner.Name = refinementFields[i];
                refiner.DisplayName = refinementFieldsFriendly[i];
                //get all unique values for this field
                List<Model.AssetPropertyValue> values = subImageMetadata.Select(ar => ar.Properties.FirstOrDefault(apv => apv.Name == refiner.Name)).ToList();
                values.Insert(0, new Model.AssetPropertyValue() { Name = refiner.Name, Value = RefinementAllValueText });
                refiner.RefinementValues = values.Where(v => v != null && !string.IsNullOrEmpty(v.Value)).Select(v => v.Value).Distinct().Select(v => new Model.RefinementValue(refiner.Name, refiner.DisplayName, v, v)).ToList();

                resultRefiners.Add(refiner);
            }
            #endregion

            //setup refiners
            if (Refiners == null)
            {
                Refiners = resultRefiners;
            }
            else
            {
                foreach (Model.RefinementGroup newGroup in resultRefiners)
                {
                    Model.RefinementGroup existingGroup = Refiners.FirstOrDefault(g => g.Name == newGroup.Name);

                    if (existingGroup == null)
                        Refiners.Add(newGroup);
                    else
                    {
                        List<Model.RefinementValue> toRemove = existingGroup.RefinementValues.Where(oldV => !newGroup.RefinementValues.Any(newV => newV.Token == oldV.Token)).ToList();
                        List<Model.RefinementValue> toAdd = newGroup.RefinementValues.Where(newV => !existingGroup.RefinementValues.Any(oldV => oldV.Token == newV.Token)).ToList();
                        toRemove.ForEach(v => existingGroup.RefinementValues.Remove(v)); //Why doesn't "RemoveRange" accept a list?
                        existingGroup.RefinementValues.AddRange(toAdd);
                    }
                }
            }

            //set image context data
            Dictionary<int, object> icd = new Dictionary<int, object>();
            foreach (Model.AssetResult asset in subImageMetadata)
            {
                icd.Add(asset.Id, asset);
            }

            DeepZoomSource = e.Result.CollectionUri;
            imageContextData = icd;
            OnPropertyChanged("ImageContextData");
            IsSearching = false;

            if (SearchCompleted != null)
                SearchCompleted(this, new EventArgs());
        }

        public event EventHandler SearchCompleted;

        private string BuildQueryXml(string searchTerm, List<Model.RefinementValue> refinements, string queryId)
        {
            /*ows_DCM_AudioBitRate(Integer)  
ows_DCM_AudioBitRateMode(Text)  
ows_DCM_AudioCodec(Text)  
ows_DCM_AudioCodecDescription(Text)  
ows_DCM_AudioDuration(Decimal)  
ows_DCM_AudioFormat(Text)  
ows_DCM_AudioLanguage(Text)  
ows_DCM_AudioResolution(Integer)  
ows_DCM_AudioSamplingRate(Decimal)  
ows_DCM_Category(Text)  
ows_DCM_ProxyUrl(Text)  
ows_DCM_VideoBitRate(Integer)  
ows_DCM_VideoBitRateMode(Text)  
ows_DCM_VideoCodec(Text)  
ows_DCM_VideoCodecDescription(Text)  
ows_DCM_VideoDisplayAspectRatio(Text)  
ows_DCM_VideoDuration(Decimal)  
ows_DCM_VideoFormat(Text)  
ows_DCM_VideoFrameRate(Decimal)  
ows_DCM_VideoIsDropFrame(Yes/No)  
ows_DCM_VideoLanguage(Text)  
ows_DCM_VideoResolution(Integer)  
ows_DCM_VideoScanType(Text)  
*/

            #region search result fields
            string[] searchFields = {
                "Write",
                "AccountName",
                "AudioBitRate",
                "AudioBitRateMode",
                "AudioCodec",
                "AudioCodecDescription",
                "AudioDuration",
                "AudioFormat",
                "AudioLanguage",
                "AudioResolution",
                "AudioSamplingRate",
                "Author",
                "Category",
                "ContentTypeKey",
                "CreatedBy",
                "Description",
                "DisplayDate",
                "DocId",
                "DuplicateHash",
                "FileExtension",
                "Filename",
                "HitHighlightedProperties",
                "HitHighlightedSummary",
                "ImageDateCreated",
                "IsDocument",
                "ItemUrl",
                "Keywords",
                "Location",
                "MetadataAuthor",
                "Path",
                "PictureHeight",
                "PictureThumbnailURL",
                "PictureURL",
                "PictureWidth",
                "PreferredName",
                "Priority",
                "Rank",
                "SecondaryFileExtension",
                "ServerRedirectedURL",
                "ServiceApplicationID",
                "Site",
                "SiteTitle",
                "Size",
                "Status",
                "Title",
                "urn:schemas.microsoft.com:fulltextqueryinfo:cataloggroup",
                "urn:schemas.microsoft.com:fulltextqueryinfo:sourcegroup",
                "urn:schemas-microsoft-com:office:office#Description",
                "urn:schemas-microsoft-com:office:office#ows_CrawlType",
                "urn:schemas-microsoft-com:office:office#ows_ListTemplate",
                "urn:schemas-microsoft-com:office:office#Subject",
                "urn:schemas-microsoft-com:office:office#Title",
                "urn:schemas-microsoft-com:publishing:Category",
                "urn:schemas-microsoft-com:publishing:CategoryTitle",
                "urn:schemas-microsoft-com:sharepoint:portal:area:CategoryUrlNavi",
                "urn:schemas-microsoft-com:sharepoint:portal:area:Path",
                "UserName",
                "UserProfile_GUID",
                "VideoBitRate",
                "VideoCodec",
                "VideoCodecDescription",
                "VideoDisplayAspectRatio",
                "VideoDuration",
                "VideoFormat",
                "VideoFrameRate",
                "VideoIsDropFrame",
                "VideoLanguage",
                "VideoResolution",
                "VideoScanType",
                "WebId",
                };
            #endregion
            StringBuilder queryXml = new StringBuilder();
            queryXml.AppendLine("<?xml version='1.0' encoding='utf-8' ?>");
            queryXml.AppendLine("<QueryPacket xmlns='urn:Microsoft.Search.Query' Revision='1000'>");
            queryXml.AppendLine("<Query domain='QDomain'>");
            queryXml.AppendLine("<SupportedFormats>");
            queryXml.AppendLine("<Format>urn:Microsoft.Search.Response.Document.Document</Format>");
            queryXml.AppendLine("</SupportedFormats>");
            queryXml.AppendLine("<Range>");
            queryXml.AppendLine("<StartAt>1</StartAt>");
            queryXml.AppendLine("<Count>100</Count>");
            queryXml.AppendLine("</Range>");
            queryXml.AppendFormat("<QueryId>{0}</QueryId>\n", queryId);
            queryXml.AppendLine("<Context>");
            if (UseFASTSearch)
            {
                queryXml.AppendFormat("<QueryText language='en-US' type='STRING'>{0}</QueryText>\n", searchTerm);
            }
            else
            {
                StringBuilder searchPhrase = new StringBuilder(searchTerm);
                foreach (var refinement in refinements)
                {
                    if(refinement.Token != RefinementAllValueText)
                        searchPhrase.AppendFormat(" {0}:{1}", refinement.Group, refinement.Token);
                }
                queryXml.AppendFormat("<QueryText language='en-US' type='STRING'>{0}</QueryText>\n", searchPhrase);
            }
            queryXml.AppendLine("</Context>");
            queryXml.AppendLine("<Properties>");
            foreach (string field in searchFields)
                queryXml.AppendFormat("<Property name=\"{0}\"/>\n", field);
            queryXml.AppendLine("</Properties>");
            queryXml.AppendLine("<EnableStemming>true</EnableStemming>");
            queryXml.AppendLine("<TrimDuplicates>true</TrimDuplicates>");

            if (UseFASTSearch)
            {
                queryXml.AppendLine("<IncludeRefinementResults>");
                queryXml.AppendLine("<Refiners>");
                queryXml.AppendFormat("<Refiner>{0}</Refiner>\n", "author");
                queryXml.AppendLine("</Refiners>");
                queryXml.AppendLine("</IncludeRefinementResults>");
                if (refinements != null && refinements.Count > 0)
                {
                    queryXml.AppendLine("<RefinementFilters>");
                    foreach (Model.RefinementValue refinement in refinements)
                    {
                        queryXml.AppendFormat("<RefinementFilter>{0}</RefinementFilter>\n", refinement.Token);
                    }
                    queryXml.AppendLine("</RefinementFilters>");
                }
            }
            queryXml.AppendLine("</Query>");
            queryXml.AppendLine("</QueryPacket>");

            return queryXml.ToString();
        }

        public bool IsSearching { get; set; }

        private string _currentSearchTerm;
        public string CurrentSearchTerm
        {
            get
            {
                return _currentSearchTerm;
            }
            set
            {
                _currentSearchTerm = value;
                OnPropertyChanged("CurrentSearchTerms");
            }
        }

        public SuperDeepZoom.SuperDeepZoom SuperDeepZoom
        {
            get
            {
                return zoom;
            }
        }

        /// <summary>
        /// Show the image overlays with image count in them (actual overlay content
        /// can be altered through templates)
        /// </summary>
        public bool SpecialImageOverlayEnabled
        {
            get
            {
                return specialImageOverlayEnabled;
            }
            set
            {
                specialImageOverlayEnabled = value;
                OnPropertyChanged("SpecialImageOverlayEnabled");
            }
        }

        /// <summary>
        /// Provides image context data for the SDZ control, image 
        /// data is retrieved from the WCF service (could get it 
        /// from any data source you like). Nothing is hard coded
        /// in the SDZ control
        /// </summary>
        public Dictionary<int, object> ImageContextData
        {
            get
            {
                return imageContextData;
            }
            set
            {
                imageContextData = value;
                OnPropertyChanged("ImageContextData");
            }
        }

        public string DeepZoomSource
        {
            get
            {
                return deepZoomSource;
            }
            set
            {
                deepZoomSource = value;
                OnPropertyChanged("DeepZoomSource");
            }
        }

        /// <summary>
        /// The image data context for the current image, used to show the 
        /// extra information popup at the bottom of the screen when zoomed in 
        /// on an image
        /// 
        /// </summary>
        public Model.AssetResult CurrentImageContext
        {
            get
            {
                return currentImageContext;
            }
            set
            {
                currentImageContext = value;
                OnPropertyChanged("CurrentImageContext");
            }
        }

        /// <summary>
        /// The search engine refinement data collection
        /// 
        /// </summary>
        private List<Model.RefinementGroup> _refiners;
        public List<Model.RefinementGroup> Refiners
        {
            get
            {
                return _refiners;
            }
            set
            {
                _refiners = value;
                OnPropertyChanged("Refiners");
                OnPropertyChanged("IsRefinementDataAvailable");
            }
        }

        public bool IsRefinementDataAvailable
        {
            get { return Refiners != null; }
        }

        public List<Model.RefinementValue> _selectedRefinmentValues = new List<Model.RefinementValue>();
        public List<Model.RefinementValue> SelectedRefinementValues
        {
            get
            {
                return _selectedRefinmentValues;
            }
            set
            {
                _selectedRefinmentValues = value;
                OnPropertyChanged("SelectedRefinementValues");
            }
        }

        /// <summary>
        /// The ID of the currently viewed image, retrieved through binding from 
        /// the SDZ
        /// </summary>
        public int? ViewedImage
        {
            get
            {
                return viewedImage;
            }
            set
            {
                viewedImage = value;

                OnPropertyChanged("CurrentImageHash");
                OnPropertyChanged("CurrentImageContext");
                OnPropertyChanged("ViewedImage");

                updatedViewedOverlay();
            }
        }

        void updatedViewedOverlay()
        {
            subImageMetadata.ForEach(p => p.Viewed = false);

            if (viewedImage != null && viewedImage.HasValue)
            {
                var sim = subImageMetadata.FirstOrDefault(p => p.Id == viewedImage.Value);
                if (sim != null)
                {
                    sim.Viewed = true;
                }
            }
        }

        /// <summary>
        /// The list of metdata for each image in the collection
        /// 
        /// </summary>
        public List<Model.AssetResult> SubImageMetadata
        {
            get
            {
                return subImageMetadata;
            }
            set
            {
                subImageMetadata = value;
                OnPropertyChanged("SubImageMetadata");
            }
        }

        /// <summary>
        /// Provides a URL that can be copied and pasted to transfer the current
        /// Flythrough can be copied to another browser window
        /// </summary>
        public string FlyThroughHash
        {
            get
            {
                string deepLink = HtmlPage.Document.DocumentUri.ToString();

                if (deepLink.IndexOf("#") != -1)
                {
                    deepLink = deepLink.Substring(0, deepLink.IndexOf("#"));
                }

                deepLink += "#" + flyThroughHash;

                return deepLink;
            }
            set
            {
                flyThroughHash = value;
                OnPropertyChanged("FlyThroughHash");
            }
        }

        /// <summary>
        /// Gets the hash link for the current image in the collection
        /// </summary>
        public string CurrentImageHash
        {
            get
            {
                if (ViewedImage != null && ViewedImage.HasValue)
                {
                    string deepLink = HtmlPage.Document.DocumentUri.ToString();

                    if (deepLink.IndexOf("#") != -1)
                    {
                        deepLink = deepLink.Substring(0, deepLink.IndexOf("#"));
                    }

                    deepLink += "#" + ViewedImage.Value.ToString();
                    return deepLink;
                }
                else
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// The fly through overlay segments which are applied to the fly through. 
        /// </summary>
        public List<FlyThroughOverlaySegment> FlyThroughOverlaySegments
        {
            get
            {
                return flyThroughOverlaySegments;
            }
            set
            {
                flyThroughOverlaySegments = value;
                OnPropertyChanged("FlyThroughOverlaySegments");
            }
        }

        public bool FlyThroughOverlayEnabled
        {
            get
            {
                return flyThroughOverlayEnabled;
            }
            set
            {
                flyThroughOverlayEnabled = value;
                OnPropertyChanged("FlyThroughOverlayEnabled");
            }
        }

        public bool ShowFlyThoughInfoPopup
        {
            get
            {
                return showFlyThoughInfoPopup;
            }
            set
            {
                showFlyThoughInfoPopup = value;
                FlyThroughOverlayEnabled = true;
                OnPropertyChanged("ShowFlyThoughInfoPopup");
            }
        }

        void zoom_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "FlyThroughHash":
                    FlyThroughHash = zoom.FlyThroughHash;
                    break;
                case "ViewedImage":
                    ViewedImage = zoom.ViewedImage;
                    break;
                case "HasUrlFlyThrough":
                    ShowFlyThoughInfoPopup = zoom.HasUrlFlyThrough;
                    break;
                case "CurrentImageContext":
                    CurrentImageContext = zoom.CurrentImageContext as Model.AssetResult;
                    break;
            }
        }
    }
}
